from threading import Condition
from threading import RLock
from atomicinteger import AtomicInteger




class BlockingQueue():
    class Node():
        def __init__(self, x):
            self.item = x
            self.next = None
    
    def __init__(self, maxsize):
        if maxsize <= 0:
            raise ValueError("maxsize must be larger than 0.")
        self.maxsize = maxsize
        self.count = AtomicInteger(0)
        self.last = self.head = BlockingQueue.Node(None)
        # locks
        self.takeLock = RLock()
        self.notEmpty = Condition(self.takeLock)
        self.putLock = RLock()
        self.notFull = Condition(self.putLock)
        
        
    def signalNotEmpty(self):
        self.takeLock.acquire()
        try:
            self.notEmpty.notify()
        finally:
            self.takeLock.release()
            
    def signalNotFull(self):
        self.putLock.acquire()
        try:
            self.notFull.notify()
        finally:
            self.putLock.release()
    
    def enqueue(self, x):
        self.last.next = BlockingQueue.Node(x)
        self.last = self.last.next
        
    def dequeue(self):
        h = self.head
        first = h.next
        h.next = h
        self.head = first
        x = first.item
        first.item = None
        return x
    
    def fullyLock(self):
        self.putLock.acquire()
        self.takeLock.acquire()
        
    def fullyUnlock(self):
        self.takeLock.release()
        self.putLock.release()
        
       
    def size(self):
        return self.count.get()
    
    def remainingCapacity(self):
        return self.maxsize - self.count.get()
    
    def put(self, e):
        if e == None:
            raise ValueError("element must not be none.")
        c = -1
        with self.putLock:
            while self.count.get() == self.maxsize:
                self.notFull.wait()
            self.enqueue(e)
            c = self.count.getAndIncrement()
            if c + 1 < self.maxsize:
                self.notFull.notify()
        if c == 0:
            self.signalNotEmpty()
            
    def take(self):
        c = -1
        with self.takeLock:
            while self.count.get() == 0:
                self.notEmpty.wait()
            x = self.dequeue()
            c = self.count.getAndDecrement()
            if (c > 1):
                self.notEmpty.notify()
        if c == self.maxsize:
            self.signalNotFull()
        return x
    
    def peek(self):
        if self.count.get() == 0:
            return None
        with self.takeLock:
            first = self.head.next
            if first == None:
                return None
            else:
                return first.item
